﻿using System;
using System.IO;
using System.Text;
using System.Xml;

namespace Eneta.Portal.Forum.Rss
{
    public class RssWriter
    {
        // Fields
        private RssModuleCollection _rssModules;
        private const string DateTimeFormatString = "ddd, dd MMM yyyy HH':'mm':'ss 'GMT'";
        private RssVersion _rssVersion;
        private XmlTextWriter _writer;
        private bool _wroteChannel;
        private bool _wroteStartDocument;
        private Formatting _xmlFormat;
        private int _xmlIndentation;

        // Methods
        public RssWriter(Stream stream)
        {
            _writer = null;
            _wroteStartDocument = false;
            _wroteChannel = false;
            _rssVersion = RssVersion.RSS20;
            _xmlFormat = (Formatting)1;
            _xmlIndentation = 2;
            _rssModules = new RssModuleCollection();
            _writer = new XmlTextWriter(stream, Encoding.GetEncoding("ISO-8859-1"));
        }

        public RssWriter(TextWriter textWriter)
        {
            this._writer = null;
            this._wroteStartDocument = false;
            this._wroteChannel = false;
            this._rssVersion = RssVersion.RSS20;
            this._xmlFormat = (Formatting)1;
            this._xmlIndentation = 2;
            this._rssModules = new RssModuleCollection();
            this._writer = new XmlTextWriter(textWriter);
        }

        public RssWriter(string fileName)
        {
            this._writer = null;
            this._wroteStartDocument = false;
            this._wroteChannel = false;
            this._rssVersion = RssVersion.RSS20;
            this._xmlFormat = (Formatting)1;
            this._xmlIndentation = 2;
            this._rssModules = new RssModuleCollection();
            this._writer = new XmlTextWriter(fileName, Encoding.GetEncoding("ISO-8859-1"));
        }

        public RssWriter(Stream stream, Encoding encoding)
        {
            this._writer = null;
            this._wroteStartDocument = false;
            this._wroteChannel = false;
            this._rssVersion = RssVersion.RSS20;
            this._xmlFormat = (Formatting)1;
            this._xmlIndentation = 2;
            this._rssModules = new RssModuleCollection();
            this._writer = new XmlTextWriter(stream, encoding);
        }

        public RssWriter(string fileName, Encoding encoding)
        {
            this._writer = null;
            this._wroteStartDocument = false;
            this._wroteChannel = false;
            this._rssVersion = RssVersion.RSS20;
            this._xmlFormat = (Formatting)1;
            this._xmlIndentation = 2;
            this._rssModules = new RssModuleCollection();
            this._writer = new XmlTextWriter(fileName, encoding);
        }

        private void BeginDocument()
        {
            if (!_wroteStartDocument)
            {
                if (_rssVersion == RssVersion.Empty)
                {
                    _rssVersion = RssVersion.RSS20;
                }
                _writer.Formatting = _xmlFormat;
                _writer.Indentation = _xmlIndentation;
                _writer.WriteStartDocument();
                if (this._rssVersion != RssVersion.RSS20)
                {
                    this._writer.WriteComment("Generated by RSS.NET: http://rss-net.sf.net");
                }
                switch (this._rssVersion)
                {
                    case RssVersion.RSS090:
                        this._writer.WriteStartElement("RDF", "rdf", "http://www.w3.org/1999/02/22-rdf-syntax-ns#");
                        break;

                    case RssVersion.RSS091:
                        this._writer.WriteStartElement("rss");
                        this._writer.WriteDocType("rss", "-//Netscape Communications//DTD RSS 0.91//EN", "http://my.netscape.com/publish/formats/rss-0.91.dtd", null);
                        this._writer.WriteAttributeString("version", "0.91");
                        break;

                    case RssVersion.RSS092:
                        this._writer.WriteStartElement("rss");
                        this._writer.WriteAttributeString("version", "0.92");
                        break;

                    case RssVersion.RSS10:
                        throw new NotSupportedException("RDF Site Summary (RSS) 1.0 is not currently supported.");

                    case RssVersion.RSS20:
                        this._writer.WriteStartElement("rss");
                        this._writer.WriteAttributeString("version", "2.0");
                        foreach (RssModule module in this._rssModules)
                        {
                            this.WriteAttribute("xmlns:" + module.NamespacePrefix, module.NamespaceURL.ToString(), true);
                        }
                        break;
                }
                this._wroteStartDocument = true;
            }
        }

        public void Close()
        {
            if (this._writer == null)
            {
                throw new InvalidOperationException("RssWriter has been closed, and can not be closed again.");
            }
            if (!this._wroteChannel)
            {
                throw new InvalidOperationException("Can't close RssWriter without first writing a channel.");
            }
            this._writer.WriteEndElement();
            this._writer.WriteEndElement();
            this._writer.Close();
            this._writer = null;
        }

        public void Write(RssChannel channel)
        {
            this.writeChannel(channel);
        }

        public void Write(RssItem item)
        {
            this.writeItem(item, -1);
        }

        private void WriteAttribute(string localName, DateTime input, bool required)
        {
            if (input != RssDefault.DateTime)
            {
                this._writer.WriteAttributeString(localName, XmlConvert.ToString(input, "ddd, dd MMM yyyy HH':'mm':'ss 'GMT'"));
            }
            else if (required)
            {
                throw new ArgumentException(localName + " can not be null.");
            }
        }

        private void WriteAttribute(string localName, int input, bool required)
        {
            if (input != -1)
            {
                this._writer.WriteAttributeString(localName, XmlConvert.ToString(input));
            }
            else if (required)
            {
                throw new ArgumentException(localName + " can not be null.");
            }
        }

        private void WriteAttribute(string localName, object input, bool required)
        {
            if (input != null)
            {
                this._writer.WriteAttributeString(localName, input.ToString());
            }
            else if (required)
            {
                throw new ArgumentException(localName + " can not be null.");
            }
        }

        private void WriteAttribute(string localName, string input, bool required)
        {
            if (input != "")
            {
                this._writer.WriteAttributeString(localName, input);
            }
            else if (required)
            {
                throw new ArgumentException(localName + " can not be null.");
            }
        }

        private void WriteAttribute(string localName, Uri input, bool required)
        {
            if (input != RssDefault.Uri)
            {
                this._writer.WriteAttributeString(localName, input.ToString());
            }
            else if (required)
            {
                throw new ArgumentException(localName + " can not be null.");
            }
        }

        private void writeChannel(RssChannel channel)
        {
            if (this._writer == null)
            {
                throw new InvalidOperationException("RssWriter has been closed, and can not be written to.");
            }
            if (channel == null)
            {
                throw new ArgumentNullException("Channel must be instanciated with data to be written.");
            }
            if (this._wroteChannel)
            {
                this._writer.WriteEndElement();
            }
            else
            {
                this._wroteChannel = true;
            }
            this.BeginDocument();
            this._writer.WriteStartElement("channel");
            this.WriteElement("title", channel.Title, true);
            this.WriteElement("description", channel.Description, true);
            this.WriteElement("link", channel.Link, true);
            if (channel.Image != null)
            {
                this._writer.WriteStartElement("image");
                this.WriteElement("title", channel.Image.Title, true);
                this.WriteElement("url", channel.Image.Url, true);
                this.WriteElement("link", channel.Image.Link, true);
                switch (this._rssVersion)
                {
                    case RssVersion.RSS091:
                    case RssVersion.RSS092:
                    case RssVersion.RSS20:
                        this.WriteElement("description", channel.Image.Description, false);
                        this.WriteElement("width", channel.Image.Width, false);
                        this.WriteElement("height", channel.Image.Height, false);
                        break;
                }
                this._writer.WriteEndElement();
            }
            switch (this._rssVersion)
            {
                case RssVersion.RSS091:
                case RssVersion.RSS092:
                case RssVersion.RSS20:
                    this.WriteElement("language", channel.Language, this._rssVersion == RssVersion.RSS091);
                    this.WriteElement("copyright", channel.Copyright, false);
                    this.WriteElement("managingEditor", channel.ManagingEditor, false);
                    this.WriteElement("webMaster", channel.WebMaster, false);
                    this.WriteElement("pubDate", channel.PubDate, false);
                    this.WriteElement("lastBuildDate", channel.LastBuildDate, false);
                    if (!(channel.Docs != ""))
                    {
                        switch (this._rssVersion)
                        {
                            case RssVersion.RSS091:
                                this.WriteElement("docs", "http://my.netscape.com/publish/formats/rss-spec-0.91.html", false);
                                break;

                            case RssVersion.RSS092:
                                this.WriteElement("docs", "http://backend.userland.com/rss092", false);
                                break;

                            case RssVersion.RSS20:
                                this.WriteElement("docs", "http://backend.userland.com/rss", false);
                                break;
                        }
                        break;
                    }
                    this.WriteElement("docs", channel.Docs, false);
                    break;

                default:
                    goto Label_038A;
            }
            this.WriteElement("rating", channel.Rating, false);
            string[] strArray = new string[] { "monday", "tuesday", "wednesday", "thursday", "friday", "saturday", "sunday" };
            for (int i = 0; i <= 6; i++)
            {
                if (channel.SkipDays[i])
                {
                    this._writer.WriteStartElement("skipDays");
                    for (int k = 0; k <= 6; k++)
                    {
                        if (channel.SkipDays[k])
                        {
                            this.WriteElement("day", strArray[k], false);
                        }
                    }
                    this._writer.WriteEndElement();
                    break;
                }
            }
            for (int j = 0; j <= 0x17; j++)
            {
                if (channel.SkipHours[j])
                {
                    this._writer.WriteStartElement("skipHours");
                    for (int m = 0; m <= 0x17; m++)
                    {
                        if (channel.SkipHours[m])
                        {
                            this.WriteElement("hour", (int)(m + 1), false);
                        }
                    }
                    this._writer.WriteEndElement();
                    break;
                }
            }
        Label_038A:
            switch (this._rssVersion)
            {
                case RssVersion.RSS092:
                case RssVersion.RSS20:
                    if (channel.Categories != null)
                    {
                        foreach (RssCategory category in channel.Categories)
                        {
                            if (category.Name != "")
                            {
                                this._writer.WriteStartElement("category");
                                this.WriteAttribute("domain", category.Domain, false);
                                this._writer.WriteString(category.Name);
                                this._writer.WriteEndElement();
                            }
                        }
                    }
                    if (channel.Cloud != null)
                    {
                        this._writer.WriteStartElement("cloud");
                        this.WriteElement("domain", channel.Cloud.Domain, false);
                        this.WriteElement("port", channel.Cloud.Port, false);
                        this.WriteElement("path", channel.Cloud.Path, false);
                        this.WriteElement("registerProcedure", channel.Cloud.RegisterProcedure, false);
                        if (channel.Cloud.Protocol != RssCloudProtocol.Empty)
                        {
                            this.WriteElement("Protocol", channel.Cloud.Protocol, false);
                        }
                        this._writer.WriteEndElement();
                    }
                    break;
            }
            if (this._rssVersion == RssVersion.RSS20)
            {
                if (channel.Generator != "")
                {
                    this.WriteElement("generator", channel.Generator, false);
                }
                else
                {
                    this.WriteElement("generator", "RSS.NET: http://www.rssdotnet.com/", false);
                }
                this.WriteElement("ttl", channel.TimeToLive, false);
                foreach (RssModule module in this._rssModules)
                {
                    if (module.IsBoundTo(channel.GetHashCode()))
                    {
                        foreach (RssModuleItem item in module.ChannelExtensions)
                        {
                            if (item.SubElements.Count == 0)
                            {
                                this.WriteElement(module.NamespacePrefix + ":" + item.Name, item.Text, item.IsRequired);
                            }
                            else
                            {
                                this.writeSubElements(item.SubElements, module.NamespacePrefix);
                            }
                        }
                        continue;
                    }
                }
            }
            if (channel.TextInput != null)
            {
                this._writer.WriteStartElement("textinput");
                this.WriteElement("title", channel.TextInput.Title, true);
                this.WriteElement("description", channel.TextInput.Description, true);
                this.WriteElement("name", channel.TextInput.Name, true);
                this.WriteElement("link", channel.TextInput.Link, true);
                this._writer.WriteEndElement();
            }
            foreach (RssItem item2 in channel.Items)
            {
                this.writeItem(item2, channel.GetHashCode());
            }
            this._writer.Flush();
        }

        private void WriteElement(string localName, DateTime input, bool required)
        {
            if (input != RssDefault.DateTime)
            {
                this._writer.WriteElementString(localName, XmlConvert.ToString(input, "ddd, dd MMM yyyy HH':'mm':'ss 'GMT'"));
            }
            else if (required)
            {
                throw new ArgumentException(localName + " can not be null.");
            }
        }

        private void WriteElement(string localName, int input, bool required)
        {
            if (input != -1)
            {
                this._writer.WriteElementString(localName, XmlConvert.ToString(input));
            }
            else if (required)
            {
                throw new ArgumentException(localName + " can not be null.");
            }
        }

        private void WriteElement(string localName, object input, bool required)
        {
            if (input != null)
            {
                this._writer.WriteElementString(localName, input.ToString());
            }
            else if (required)
            {
                throw new ArgumentException(localName + " can not be null.");
            }
        }

        private void WriteElement(string localName, string input, bool required)
        {
            if (input != "")
            {
                this._writer.WriteElementString(localName, input);
            }
            else if (required)
            {
                throw new ArgumentException(localName + " can not be null.");
            }
        }

        private void WriteElement(string localName, Uri input, bool required)
        {
            if (input != RssDefault.Uri)
            {
                this._writer.WriteElementString(localName, input.ToString());
            }
            else if (required)
            {
                throw new ArgumentException(localName + " can not be null.");
            }
        }

        private void writeItem(RssItem item, int channelHashCode)
        {
            if (this._writer == null)
            {
                throw new InvalidOperationException("RssWriter has been closed, and can not be written to.");
            }
            if (item == null)
            {
                throw new ArgumentNullException("Item must be instanciated with data to be written.");
            }
            if (!this._wroteChannel)
            {
                throw new InvalidOperationException("Channel must be written first, before writing an item.");
            }
            this.BeginDocument();
            this._writer.WriteStartElement("item");
            switch (this._rssVersion)
            {
                case RssVersion.RSS090:
                case RssVersion.RSS091:
                case RssVersion.RSS10:
                    this.WriteElement("title", item.Title, true);
                    this.WriteElement("description", item.Description, false);
                    this.WriteElement("link", item.Link, true);
                    goto Label_0256;

                case RssVersion.RSS092:
                    break;

                case RssVersion.RSS20:
                    if ((item.Title == "") && (item.Description == ""))
                    {
                        throw new ArgumentException("item title and description cannot be null");
                    }
                    break;

                default:
                    goto Label_0256;
            }
            this.WriteElement("title", item.Title, false);
            this.WriteElement("description", item.Description, false);
            this.WriteElement("link", item.Link, false);
            if (item.Source != null)
            {
                this._writer.WriteStartElement("source");
                this.WriteAttribute("url", item.Source.Url, true);
                this._writer.WriteString(item.Source.Name);
                this._writer.WriteEndElement();
            }
            if (item.Enclosure != null)
            {
                this._writer.WriteStartElement("enclosure");
                this.WriteAttribute("url", item.Enclosure.Url, true);
                this.WriteAttribute("length", item.Enclosure.Length, true);
                this.WriteAttribute("type", item.Enclosure.Type, true);
                this._writer.WriteEndElement();
            }
            foreach (RssCategory category in item.Categories)
            {
                if (category.Name != "")
                {
                    this._writer.WriteStartElement("category");
                    this.WriteAttribute("domain", category.Domain, false);
                    this._writer.WriteString(category.Name);
                    this._writer.WriteEndElement();
                }
            }
        Label_0256:
            if (this._rssVersion == RssVersion.RSS20)
            {
                this.WriteElement("author", item.Author, false);
                this.WriteElement("comments", item.Comments, false);
                if ((item.Guid != null) && (item.Guid.Name != ""))
                {
                    this._writer.WriteStartElement("guid");
                    try
                    {
                        this.WriteAttribute("isPermaLink", (bool)item.Guid.PermaLink, false);
                    }
                    catch
                    {
                    }
                    this._writer.WriteString(item.Guid.Name);
                    this._writer.WriteEndElement();
                }
                this.WriteElement("pubDate", item.PubDate, false);
                foreach (RssModule module in this._rssModules)
                {
                    if (module.IsBoundTo(channelHashCode))
                    {
                        foreach (RssModuleItemCollection items in module.ItemExtensions)
                        {
                            if (items.IsBoundTo(item.GetHashCode()))
                            {
                                this.writeSubElements(items, module.NamespacePrefix);
                            }
                        }
                        continue;
                    }
                }
            }
            this._writer.WriteEndElement();
            this._writer.Flush();
        }

        private void writeSubElements(RssModuleItemCollection items, string NamespacePrefix)
        {
            foreach (RssModuleItem item in items)
            {
                if (item.SubElements.Count == 0)
                {
                    this.WriteElement(NamespacePrefix + ":" + item.Name, item.Text, item.IsRequired);
                }
                else
                {
                    this._writer.WriteStartElement(NamespacePrefix + ":" + item.Name);
                    this.writeSubElements(item.SubElements, NamespacePrefix);
                    this._writer.WriteEndElement();
                }
            }
        }

        // Properties
        public RssModuleCollection Modules
        {
            get
            {
                return this._rssModules;
            }
            set
            {
                this._rssModules = value;
            }
        }

        public RssVersion Version
        {
            get
            {
                return this._rssVersion;
            }
            set
            {
                if (this._wroteStartDocument)
                {
                    throw new InvalidOperationException("Can't change version number after data has been written.");
                }
                this._rssVersion = value;
            }
        }

        public Formatting XmlFormat
        {
            get
            {
                return this._xmlFormat;
            }
            set
            {
                if (this._wroteStartDocument)
                {
                    throw new InvalidOperationException("Can't change XML formatting after data has been written.");
                }
                this._xmlFormat = value;
            }
        }

        public int XmlIndentation
        {
            get
            {
                return this._xmlIndentation;
            }
            set
            {
                if (this._wroteStartDocument)
                {
                    throw new InvalidOperationException("Can't change XML indentation after data has been written.");
                }
                if (value < 0)
                {
                    throw new ArgumentException("Setting this property to a negative value.");
                }
                this._xmlIndentation = value;
            }
        }
    }
}
